<#
Copyright 2012 Chris Duck

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
#>

<#
.SYNOPSIS

	Gets a listing of packages from a NuGet server, along with the locally installed version (if applicable).

.DESCRIPTION

.PARAMETER Id

	The package Id to retrieve.  If none is specified all packages are returned from the server.

.PARAMETER Version

	The specific version of a package to retreive. This must be either a System.Version object or a string in one of the following formats:
	
	1.1
	1.2.1
	1.3.3.1
	
	If none is specified, only the latest version is returned.

.PARAMETER AllVersions

	A switch parameter that specifies to return all versions of packages from the server.

.PARAMETER Source

	The name of the NuGet source server to retrieve packages from.  The available sources can be located using Get-NuGetSource.  If no source is specified, the source marked as Default will be used.
	
	See Get-Help Get-NuGetSource for more information on working with sources.

.LINK
	Install-NuGetPackage
	Update-NuGetPackage
	Uninstall-NuGetPackage
	
	Get-NuGetSource
	Add-NuGetSource
	Remove-NuGetSource
	Set-NuGetSource

.EXAMPLE

	Get-NuGetPackage -Id PS-NuGet
	
	Returns the latest version of the package with Id = Ps-NuGet from the default source.
#>
function Get-NuGetPackage {
	[CmdletBinding(DefaultParameterSetName="FromServer")]
	param(
		[Parameter(Mandatory=$false,Position=0)]
		[ValidateNotNullOrEmpty()]
		[string]$Id,
		
		[Parameter(Mandatory=$false,ParameterSetname="FromServer")]
		[ValidateNotNullOrEmpty()]
		[Version]$Version,
		
		<#
		I haven't tested this functionality, so I'm removing it from the initial release
		[Parameter(Mandatory=$false)]
		[switch]$IncludePrerelease,
		#>
		
		[Parameter(Mandatory=$false,ParameterSetName="FromServer")]
		[switch]$AllVersions,
		
		[Parameter(Mandatory=$false,ParameterSetName="FromServer")]
		[ValidateNotNullOrEmpty()]
		[string]$Source,
		
		[Parameter(Mandatory=$false,ParameterSetName="LocalOnly")]
		[switch]$LocalOnly
	)
	$OutPackages = New-Object System.Collections.ArrayList
	if($LocalOnly) {
		try {
			$LocalPackages = GetLocalPackage @PSBoundparameters
			$LocalPackages | %{
				$OutPackage = New-Object NuGet.Server.DataServices.Package
				$LocalPackage = $_
				$NuSpec = GetPackageNuSpec $LocalPackage
				"Authors", "Id", "Title", "RequireLicenseAcceptance", "Description", "ReleaseNotes", "tags" | %{
					$OutPackage."$_" = $NuSpec.Package.Metadata."$_"
				}
				$OutPackage.InstalledVersion = $NuSpec.Package.Metadata.Version
				if($NuSpec.Package.Metadata.Dependencies.childnodes.count -gt 0) {
					$DependencyArray = $NuSpec.Package.Metadata.Dependencies.childnodes | %{
						"{0}:{1}" -f $_.Id, $_.Version
					}
					$OutPackage.Dependencies = [string]::Join("|", $DependencyArray)
				}
				$OutPackages.Add($OutPackage) > $null
			}
		} finally {
			if($LocalPackages) {
				$LocalPackages | %{
					try {$_.Close()} catch {}
				}
			}
		}
	} else {
		#Get packages from NuGet server and add our properties
		try {
			try {
				$ServerPackages = GetPackagesFromServer @PSBoundParameters
			} catch [System.Reflection.TargetInvocationException] {
				#Strip off this powershell exception so we can get to a better error message
				throw $_.exception.innerexception
			} catch {
				throw $_.exception
			}
			
			#Make sure something was returned from the server so we don't pipeline $null as a package
			if($ServerPackages) {
				$ServerPackages | %{
					#Get installed version
					$_.InstalledVersion = $null
					$RequestedID = $_.Id
					try {
						$LocalPackage = GetLocalPackage -Id $_.Id
						if($LocalPackage) {
							$NuSpec = GetPackageNuSpec $LocalPackage
							$_.InstalledVersion = [Version]$NuSpec.package.metadata.Version
						} else {
							Write-Debug "Get-NuGetPackage: Package '$($_.Id)' not installed locally"
						}
					} catch {
						throw New-Object Exception("Error loading local package info for $($RequestedID): $_", $_.Exception)
					} finally {
						if($LocalPackage) {
							$LocalPackage.Close()
						}
					}
					#Add this package to our output list
					$OutPackages.Add($_) > $null
				}
			}
		} catch [System.Data.Services.Client.DataServiceQueryException] {
			#Catch a data service query exception and attempt to get the return code from the server out of it.
			$StatusCode = $_.Exception.InnerException.StatusCode
			throw New-Object Exception("Error retrieving packages: '$($_.Exception.Message)' Server returned $StatusCode", $_.exception)
		} catch {
			throw New-Object Exception("An unknown error occurred retrieving packages: $($_.Exception.Message)", $_.Exception)
		}
	}
	#Do populate fields that are common to both server and local package lookup here
	$OutPackages | %{
		#Get Subscribed value
		$_.Subscribed = [bool](Get-NuGetSubscription -Id $_.Id)
		
		$ToolsRepository = GetToolsRepository
		$ToolsFolder = Join-Path $ToolsRepository $_.Id
		$InstalledSourceFile = Join-Path $ToolsFolder "source.psnugetxml"
		if( (Test-Path $InstalledSourceFile) ) {
			$SourceXml = Import-Clixml $InstalledSourceFile -ErrorAction SilentlyContinue
			if($SourceXml) {
				$_.InstalledSource = New-Object NuGet.Server.DataServices.Source($SourceXml.Name, $SourceXml.Uri, $SourceXml.Default)
			}
		}
		
		$_ #Emit the package to the pipeline
	}
}

<#
.SYNOPSIS

	Installs a NuGet package into the local PowerShell module folder.

.DESCRIPTION

.PARAMETER Id

	The package Id to retrieve.  This parameter is mandatory and can be specified via the pipeline.
	
	Get-NuGetPackage -Id PS-NuGet | Install-NuGetPackage

.PARAMETER Version

	The specific version of a package to retreive. This must be either a System.Version object or a string in one of the following formats:
	
	1.1
	1.2.1
	1.3.3.1
	
	If none is specified, the latest version is installed.  This parameter can also be specified via the pipeline.

.PARAMETER Source

	The name of the NuGet source server to retrieve packages from.  If no source is specified, the source marked as Default will be used.
	
	The available sources can be located using Get-NuGetSource.
	See Get-Help Get-NuGetSource for more information on working with sources.
	
	This parameter must be explicitly defined if the package is not on the default source server, even when the package information is specified via the pipeline.
	
.PARAMETER IgnoreMissingDependencies

	Switch parameter to tell PS-NuGet to install a package even if it's listed dependencies cannot be resolved.  It is unlikely a package with a missing dependency that is installed with this switch will function correctly.

.LINK
	Install-NuGetPackage
	Update-NuGetPackage
	Uninstall-NuGetPackage
	
	Get-NuGetSource
	Add-NuGetSource
	Remove-NuGetSource
	Set-NuGetSource

.EXAMPLE

	Install-NuGetPackage -Id PS-NuGet
	
	Installs the latest version of the package with Id = Ps-NuGet from the default source.

.NOTE

	It is an error to install a package that is already installed.  Use Update-NuGetPackage to update to the latest version.
#>
function Install-NuGetPackage {
	[CmdletBinding(SupportsShouldProcess=$true)]
	param(
		[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true,Position=0)]
		[ValidateNotNullOrEmpty()]
		[string]$Id,
		
		[Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
		[ValidateNotNullOrEmpty()]
		[Version]$Version,
		
		<#
		I haven't tested this functionality, so I'm removing it from the initial release
		[Parameter(Mandatory=$false)]
		[switch]$IncludePrerelease,
		#>
		
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[string]$Source,
		
		[Parameter(Mandatory=$false)]
		[switch]$IgnoreMissingDependencies
	)
	begin {
		$PSBoundParameters.Remove("whatif") > $null
		$PSBoundParameters.Remove("confirm") > $null
		$SharedPSCmdlet = $PsCmdlet
	}
	process {
		InstallPSPackageInternal @PSBoundParameters -SharedPSCmdlet $SharedPSCmdlet
	}
}

<#
  Internal implementation of Install-NuGetPackage.  This is split out to hide the
	-SkipDependencies parameter which should only be used after dependencies have been resolved.
#>
function InstallPSPackageInternal {
	param(
		[Parameter(Mandatory=$true)]
		[ValidateNotNullOrEmpty()]
		[string]$Id,
		
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[Version]$Version,
		
		[Parameter(Mandatory=$false)]
		[switch]$IncludePrerelease,
		
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[string]$Source,
		
		[Parameter(Mandatory=$false)]
		[switch]$IgnoreMissingDependencies,
		
		[Parameter(Mandatory=$false)]
		[switch]$SkipDependencies,
		
		[Parameter(Mandatory=$true)]
		$SharedPSCmdlet
	)
	$LocalRepository = GetLocalRepository
	$ToolsRepository = GetToolsRepository
	$GetPackageParameters = New-Object Collections.Hashtable($PSBoundParameters)
	#Remove the parameters from here that aren't valid for Get-NuGetPackage
	"SharedPSCmdlet", "IgnoreMissingDependencies", "SkipDependencies" | %{
		if($GetPackageParameters.ContainsKey($_)) {
			$GetPackageParameters.Remove($_) > $null
		}
	}
	$PsPackageToInstall = @(Get-NuGetPackage @GetPackageParameters)
	if($PsPackageToInstall.Count -ne 1) {
		Write-Error "Unable to install $Id, Get-NuGetPackage returned $($PsPackageToInstall.count) packages, should be 1"
		return
	}
	
	if($PsPackageToInstall[0].InstalledVersion) {
		Write-Error "Package $($PsPackageToInstall[0].Id) is already installed.  Use Update-NuGetPackage instead."
		return
	}
	
	$DependencyList = BuildDependencyList $PsPackageToInstall[0]
	if($DependencyList.Missing.Count -gt 0) {
		$Missing = @($DependencyList.Missing | %{ "{0} >= ({1})" -f $_.Id, $_.Version})
		if($IgnoreMissingDependencies) {
			Write-Warning "The following dependencies of $($PsPackageToInstall[0].Id) could not be resolved: $([string]::Join(', ', $Missing)), but IgnoreMissingDependencies was specified so packages are being installed anyway."
		} else {
			throw "The following dependencies could not be resolved: $([string]::Join(', ', $Missing))"
		}
	}
	
	if($DependencyList.Needed.Count -gt 0 -and !$SkipDependencies) {
		$DependencyList.Needed | %{
			$NeededPackage = Get-NuGetPackage -Id $_.Id -Source $PsPackageToInstall[0].Source.Name
			if($NeededPackage.InstalledVersion) {
				Write-Debug "Updating dependency $($_.Id)"
				Write-Verbose "Updating dependency $($_.Id)"
				#Skip dependencies because all needed dependencies will be in this list.
				UpdatePSPackageInternal -Id $_.Id -SkipDependencies -IgnoreMissingDependencies:$IgnoreMissingDependencies -SharedPSCmdlet $SharedPSCmdlet
			} else {
				Write-Debug "Installing dependency $($_.Id)"
				Write-Verbose "Installing dependency $($_.Id)"
				#Skip dependencies because all needed dependencies will be in this list.
				InstallPSPackageInternal -Id $_.Id -Source $PsPackageToInstall[0].Source.Name -SkipDependencies -IgnoreMissingDependencies:$IgnoreMissingDependencies -SharedPSCmdlet $SharedPSCmdlet
			}
		}
	}
	
	if($SharedPSCmdlet.ShouldProcess($PsPackageToInstall[0].Id, "Install package")) {
		#Download the package
		$NuPkgFileName = DownloadPackageFromServer -PSPackage $PsPackageToInstall[0] -PackageRepositoryPath $LocalRepository
		
		$NuPkg = GetLocalPackage -ModuleRepository $LocalRepository -Id $PsPackageToInstall[0].Id
		try {
			$ModulePath = Join-Path $LocalRepository $PsPackageToInstall[0].Id
			$ToolsPath = Join-Path $ToolsRepository $PsPackageToInstall[0].Id
			if( !(Test-Path $ModulePath) ) {
				mkdir $ModulePath -Force | Out-Null
			}
			if( !(Test-Path $ToolsPath) ) {
				mkdir $ToolsPath -Force | Out-Null
			}
			Write-Verbose "Installing $($PsPackageToInstall[0].Id)"
			ExplodePackage -NuPkg $NuPkg -ModulePath $ModulePath -ToolsPath $ToolsPath
			$SourceFile = Join-Path $ToolsPath "source.psnugetxml"
			$PsPackageToInstall[0].Source | Export-Clixml -Path $SourceFile -Force -ErrorAction SilentlyContinue
			
			$InitPS1 = Join-Path $ToolsPath "init.ps1"
			if( (Test-Path $InitPS1) ) {
				Write-Debug "Running $InitPS1"
				&$InitPS1 -InstallPath $ModulePath -ToolsPath $ToolsPath
			}
			$InstallPS1 = Join-Path $ToolsPath "install.ps1"
			if( (Test-Path $InstallPS1) ) {
				Write-Debug "Running $InstallPS1"
				&$InstallPS1 -InstallPath $ModulePath -ToolsPath $ToolsPath -Package $null
			}
			
		} finally {
			if($NuPkg) {
				$NuPkg.Close()
			}
		}
	}
}


<#
.SYNOPSIS

	Updates a NuGet package that has already been installed into the local PowerShell module folder.

.DESCRIPTION

.PARAMETER Id

	The package Id to update.  This parameter is mandatory and can be specified via the pipeline.
	
	Get-NuGetPackage -Id PS-NuGet | Update-NuGetPackage

.PARAMETER Version

	The specific version of a package to retreive. This must be either a System.Version object or a string in one of the following formats:
	
	1.1
	1.2.1
	1.3.3.1
	
	If none is specified, the latest version is installed.  This parameter can also be specified via the pipeline.

.PARAMETER Source

	The name of the NuGet source server to retrieve packages from.  If no source is specified, the source marked as Default will be used.
	
	The available sources can be located using Get-NuGetSource.
	See Get-Help Get-NuGetSource for more information on working with sources.
	
	This parameter must be explicitly defined if the package is not on the default source server, even when the package information is specified via the pipeline.

.PARAMETER IgnoreMissingDependencies

	Switch parameter to tell PS-NuGet to install a package even if it's listed dependencies cannot be resolved.  It is unlikely a package with a missing dependency that is installed with this switch will function correctly.

.LINK
	Install-NuGetPackage
	Update-NuGetPackage
	Uninstall-NuGetPackage
	
	Get-NuGetSource
	Add-NuGetSource
	Remove-NuGetSource
	Set-NuGetSource

.EXAMPLE

	Update-NuGetPackage -Id PS-NuGet
	
	Updates the package with Id = Ps-NuGet to the latest version from the default source.

.NOTE

	It is an error to update a package that is not installed.  Use Install-NuGetPackage to install the latest version.
	
#>
function Update-NuGetPackage {
	[CmdletBinding(SupportsShouldProcess=$true)]
	param(
		[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true,Position=0)]
		[ValidateNotNullOrEmpty()]
		[string]$Id,
		
		[Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
		[ValidateNotNullOrEmpty()]
		[Version]$Version,
		
		<#
		I haven't tested this functionality, so I'm removing it from the initial release
		[Parameter(Mandatory=$false)]
		[switch]$IncludePrerelease,
		#>
		
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[string]$Source,
		
		[Parameter(Mandatory=$false)]
		[switch]$IgnoreMissingDependencies
	)
	begin {
		$PSBoundParameters.Remove("whatif") > $null
		$PSBoundParameters.Remove("confirm") > $null
		$SharedPSCmdlet = $PsCmdlet
	}
	process {
		UpdatePSPackageInternal @PSBoundParameters -SharedPSCmdlet $SharedPSCmdlet
	}
}

<#
  Internal implementation of Update-NuGetPackage.  This is split out to hide the
	-SkipDependencies parameter which should only be used after dependencies have been resolved.
#>
function UpdatePSPackageInternal {
		param(
		[Parameter(Mandatory=$true)]
		[ValidateNotNullOrEmpty()]
		[string]$Id,
		
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[Version]$Version,
		
		[Parameter(Mandatory=$false)]
		[switch]$IncludePrerelease,
		
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[string]$Source,
		
		[Parameter(Mandatory=$false)]
		[switch]$IgnoreMissingDependencies,
		
		[Parameter(Mandatory=$false)]
		[switch]$SkipDependencies,
		
		[Parameter(Mandatory=$true)]
		$SharedPSCmdlet
	)
	
	#Download new file
	#Explode package
	$LocalRepository = GetLocalRepository
	$GetPackageParameters = New-Object Collections.Hashtable($PSBoundParameters)
	#Remove the parameters from here that aren't valid for Get-NuGetPackage
	"SharedPSCmdlet", "IgnoreMissingDependencies", "SkipDependencies" | %{
		if($GetPackageParameters.ContainsKey($_)) {
			$GetPackageParameters.Remove($_) > $null
		}
	}
	if(!$Source) {
		$PackageSearch = @(Get-NuGetPackage @GetPackageParameters -LocalOnly)
		if($PackageSearch.Count -gt 0) {
			$PackageSearch | ?{$_.Id -eq $Id } | %{
				$GetPackageParameters.Add("Source", $_.InstalledSource.Name)
			}
		}
	}
	$PackageSearch = @(Get-NuGetPackage @GetPackageParameters)
	if($PackageSearch.Count -ne 1) {
		Write-Error "Unable to update $Id, Get-NuGetPackage returned $($PackageSearch.count) packages, should be 1"
		return
	}
	
	$PackageToUpdate = $PackageSearch[0]
	
	if($PackageToUpdate.ServerVersion -gt $PackageToUpdate.InstalledVersion) {
		$DependencyList = BuildDependencyList $PackageToUpdate
		if($DependencyList.Missing.Count -gt 0) {
			$Missing = @($DependencyList.Missing | %{ "{0} >= ({1})" -f $_.Id, $_.Version})
			if($IgnoreMissingDependencies) {
				Write-Warning "The following dependencies of $($PackageToUpdate.Id) could not be resolved: $([string]::Join(', ', $Missing)), but IgnoreMissingDependencies was specified so packages are being updated anyway."
			} else {
				throw "The following dependencies could not be resolved: $([string]::Join(', ', $Missing))"
			}
		}
		
		if($DependencyList.Needed.Count -gt 0 -and !$SkipDependencies) {
			$DependencyList.Needed | %{
				$NeededPackage = Get-NuGetPackage -Id $_.Id -Source $PackageToUpdate.Source.Name
				if($NeededPackage.InstalledVersion) {
					Write-Debug "Updating dependency $($_.Id)"
					Write-Verbose "Updating dependency $($_.Id)"
					#Skip dependencies because all needed dependencies will be in this list.
					#UpdatePSPackageInternal -Id $_.Id -Source $PackageToUpdate.Source.Name -SkipDependencies -IgnoreMissingDependencies:$IgnoreMissingDependencies -SharedPSCmdlet $SharedPSCmdlet
					#Don't specify a source for dependencies, get them from where the came from the first time.
					UpdatePSPackageInternal -Id $_.Id -SkipDependencies -IgnoreMissingDependencies:$IgnoreMissingDependencies -SharedPSCmdlet $SharedPSCmdlet
				} else {
					Write-Debug "Installing dependency $($_.Id)"
					Write-Verbose "Installing dependency $($_.Id)"
					#Skip dependencies because all needed dependencies will be in this list.
					InstallPSPackageInternal -Id $_.Id -Source $PackageToUpdate.Source.Name -SkipDependencies -IgnoreMissingDependencies:$IgnoreMissingDependencies -SharedPSCmdlet $SharedPSCmdlet
				}
			}
		}
		
		if($SharedPSCmdlet.ShouldProcess($PackageToUpdate.Id, "Update package")) {
			#Download the package
			$NuPkgFileName = DownloadPackageFromServer -PSPackage $PackageToUpdate -PackageRepositoryPath $LocalRepository
			
			#Clean existing package (excluding new nupkg file, need to deal with self update locked DLL as well)
			if($Id -eq "PS-Nuget" ) {
				throw "Self update not implemented"
			}
			UninstallPackageInternal -Id $PackageToUpdate.Id -Exclude $NuPkgFileName -SharedPSCmdlet $SharedPSCmdlet
					
			$NuPkg = GetLocalPackage -ModuleRepository $LocalRepository -Id $PackageToUpdate.Id
			try {
				$ModulePath = Join-Path $LocalRepository $PackageToUpdate.Id
				$ToolsPath = Join-Path (GetToolsRepository) $PackageToUpdate.Id
				#Recreate the module and tools folders since we just deleted them.
				if( !(Test-Path $ModulePath) ) {
					mkdir $ModulePath -Force | Out-Null
				}
				if( !(Test-Path $ToolsPath) ) {
					mkdir $ToolsPath -Force | Out-Null
				}
				Write-Verbose "Extracting $($PackageToUpdate.Id)"
				ExplodePackage -NuPkg $NuPkg -ModulePath $ModulePath -ToolsPath $ToolsPath
				$SourceFile = Join-Path $ToolsPath "source.psnugetxml"
				$PackageToUpdate.Source | Export-Clixml -Path $SourceFile -Force -ErrorAction SilentlyContinue
				
				#If the package includes an "install.ps1" file in its tools folder, run it
				$InstallPS1 = Join-Path $ToolsPath "install.ps1"
				if( (Test-Path $InstallPS1) ) {
					&$InstallPS1 -InstallPath $ModulePath -ToolsPath $ToolsPath -Package $null
				}
			} finally {
				if($NuPkg) {
					$NuPkg.Close()
				}
			}
		}
	} else {
		Write-Debug "UpdatePSPackageInternal: Package '$($PackageToUpdate.Id)' is already at the latest version."
	}
}


<#
.SYNOPSIS

	Uninstalls a NuGet package from the local PowerShell module folder.

.DESCRIPTION

.PARAMETER Id

	The package Id to uninstall.  This parameter is mandatory and can be specified via the pipeline.
	
	Get-NuGetPackage -Id PS-NuGet | Uninstall-NuGetPackage

.LINK
	Install-NuGetPackage
	Update-NuGetPackage
	Uninstall-NuGetPackage
	
	Get-NuGetSource
	Add-NuGetSource
	Remove-NuGetSource
	Set-NuGetSource

.EXAMPLE

	Uninstall-NuGetPackage -Id PS-NuGet
	
	Removes the package with Id = Ps-NuGet from the local PowerShell module folder.

#>
function Uninstall-NuGetPackage {
	[CmdletBinding(SupportsShouldProcess=$true)]
	param(
		[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true)]
		[ValidateNotNullOrEmpty()]
		[string]$Id
	)
	begin {
		$PSBoundParameters.Remove("whatif") > $null
		$PSBoundParameters.Remove("confirm") > $null
		$SharedPSCmdlet = $PsCmdlet
	}
	process {
		UninstallPackageInternal @PSBoundParameters -SharedPSCmdlet $SharedPSCmdlet
	}
}

<#
  Internal implementation of Uninstall-NuGetPackage.  This is split out to hide the
	-Exclude parameter that is used to exclude files from deletion (specifically for self-update
	as the NuGet.Client.dll file will be locked by the module.
#>
function UninstallPackageInternal {
	param(
		[Parameter(Mandatory=$true)]
		[ValidateNotNullOrEmpty()]
		[string]$Id,
		
		[Parameter(Mandatory=$false)]
		[String[]]$Exclude = @(),
		
		[Parameter(Mandatory=$true)]
		$SharedPSCmdlet
	)
	$ModulePath = Join-Path (GetLocalRepository) $Id
	$ToolsPath = Join-Path (GetToolsRepository) $Id
		
	if($Exclude.Count -gt 0) {
		#If we're excluding stuff, don't delete the module folder, just all the junk in it
		$ModulePath = Join-Path $ModulePath "\*"
	}

	if( (Test-Path $ModulePath) ) {
		if($SharedPSCmdlet.ShouldProcess($ModulePath, "Delete all module files")) {
			$UninstallPS1 = Join-Path $ToolsPath "uninstall.ps1"
			if( (Test-Path $UninstallPS1) ) {
					&$UninstallPS1 -InstallPath $ModulePath -ToolsPath $ToolsPath -Package $null
			}

			del $ModulePath -Force -Recurse -Exclude $Exclude
			if( (Test-Path $ToolsPath) ) {
				del $ToolsPath -Force -Recurse
			}
		}
	} else {	
		Write-Debug "Uninstall-NuGetPackage: Path $ModulePath not found"
	}
}

<#
.SYNOPSIS

	Gets a list of the configured sources for PS-NuGet.

.DESCRIPTION

.PARAMETER Name

	The name of the source to retrieve. If this parameter is not specified, all configured sources are returned.

.PARAMETER Default

	This switch parameter causes Get-NuGetSource to only return the source configured as default.

.LINK
	Get-NuGetSource
	Add-NuGetSource
	Remove-NuGetSource
	Set-NuGetSource

.EXAMPLE

	Get-NuGetSource
	
	Returns all the configured sources for PS-NuGet

#>
function Get-NuGetSource {
	[CmdletBinding(DefaultParameterSetName="ByName")]
	param(
		[Parameter(Mandatory=$false,ParameterSetName="ByName",Position=0)]
		[string]$Name,
		
		[Parameter(Mandatory=$false,ParameterSetName="Default")]
		[switch]$Default
	)
	$NuGetConfigXML = GetNuGetConfigXML

	if($NuGetConfigXML) {
		$DefaultSource = $NuGetConfigXML.configuration.packageSources.default
		if($PSCmdlet.ParameterSetName -eq "Default") {
			return Get-NuGetSource -Name $DefaultSource
		} else {
			if($NuGetConfigXML.configuration.packageSources.ChildNodes.Count -gt 0) {
				$NuGetConfigXML.configuration.packageSources.source | %{
					$Out = New-Object NuGet.Server.DataServices.Source($_.Name, $_.Uri, ($_.Name -eq $DefaultSource))
					$Out
				} | ?{
					if($Name) {
						$_.Name -like $Name
					} else {
						$true
					}
				}
			} else {
				Write-Debug "Get-NuGetSource: No packages are configured"
			}
		}
	}
}

<#
.SYNOPSIS

	Adds a new source for PS-NuGet.

.DESCRIPTION

.PARAMETER Name

	The name of the new source.  This is how the source will be referenced when working with packages and must be unique.

.PARAMETER Uri

	The location of the NuGet server to add to the sources list.  This should be the URL to the root of the OData feed, usually http://servername/nuget.

.PARAMETER Default

	This switch parameter will set the new source as the default source. If an existing source is marked as default, the new source will replace it as the default source.

.LINK
	Get-NuGetSource
	Add-NuGetSource
	Remove-NuGetSource
	Set-NuGetSource

.EXAMPLE

	Add-NuGetSource -Name OurLocalPackages -Uri http://psnuget.example.com/nuget -Default
	
	Adds a new source called "OurLocalPackages" that is hosted at http://psnuget.example.com/nuget and marks it as the default source to use when no source is explicitly specified.

.NOTE

	The first source added will always be marked as the default source.  Add additional sources and then use Set-NuGetSource to change the default.
	
#>
function Add-NuGetSource {
	param(
		[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
		[ValidateNotNullOrEmpty()]
		[string]$Name,
		
		[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
		[ValidateNotNullOrEmpty()]
		[Uri]$Uri,
		
		[Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
		[switch]$Default
	)
	begin {
		$NuGetConfigXML = GetNuGetConfigXML -Create
		if(!$NuGetConfigXml) {
			#I don't expect this will ever be called as the GetNuGetConfigXML function
			#  throws an exception if it can't load an existing file or it just creates an
			#  object which is unlikely to fail.
			throw "Unable to open or create config file"
		}
	}
	process {
		#We can't just use Get-NuGetSource because it loads stale data from the XML file,
		#  which we aren't saving till the end if this is pipelined.
		$ExistingNode = $NuGetConfigXML.SelectSingleNode("/configuration/packageSources/source[@name='$name']")
		if($ExistingNode) {
			#Write an error
			Write-Error "Source $Name already exists. Use Set-NuGetSource to change its properties"
			#return will continue processing the pipeline
			return
		}
		$PackageSourcesNode = $NuGetConfigXML.configuration.packageSources
		if($Default -or $PackageSourcesNode.ChildNodes.Count -eq 0) {
			$PackageSourcesNode.SetAttribute("default", $Name)
		}
		$NewSourceNode = $NuGetConfigXML.CreateElement("source")
		$NewSourceNode.SetAttribute("name", $Name)
		$NewSourceNode.SetAttribute("URI", $Uri.ToString())
		$PackageSourcesNode.AppendChild($NewSourceNode) > $null
	}
	end {
		SaveNuGetConfigXML $NuGetConfigXML
	}
}

<#
.SYNOPSIS

	Removes a configured source for PS-NuGet.

.DESCRIPTION

.PARAMETER Name

	The name of the new source.  This is how the source will be referenced when working with packages and must be unique.

.LINK
	Get-NuGetSource
	Add-NuGetSource
	Remove-NuGetSource
	Set-NuGetSource

.EXAMPLE

	Add-NuGetSource -Name OurLocalPackages -Uri http://psnuget.example.com/nuget -Default
	
	Adds a new source called "OurLocalPackages" that is hosted at http://psnuget.example.com/nuget and marks it as the default source to use when no source is explicitly specified.

.NOTE

	If the source to be removed is the default source, a randomly selected source will be marked as default.  If all sources are removed, only the Uninstall-NuGetPackage package management command will work.
	
#>
function Remove-NuGetSource {
	[CmdletBinding()]
	param(
		[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
		[ValidateNotNullOrEmpty()]
		[string]$Name
	)
	begin {
		$NuGetConfigXML = GetNuGetConfigXML -Create
		if(!$NuGetConfigXml) {
			#I don't expect this will ever be called as the GetNuGetConfigXML function
			#  throws an exception if it can't load an existing file or it just creates an
			#  object which is unlikely to fail.
			throw "Unable to open or create config file"
		}
	}
	process {
		$PackageSourcesNode = $NuGetConfigXML.configuration.packageSources
		$ExistingNode = $PackageSourcesNode.source | ?{$_.name -eq $Name}
		if(!$ExistingNode) {
			#Hack to get around -WarningAction bug
			$PsCmdlet.WriteWarning("Unable to remove source $Name, name not found in existing sources.")
		} else {
			$PackageSourcesNode.RemoveChild($ExistingNode) > $null
			if($PackageSourcesNode.ChildNodes.Count -eq 0) {
				$PackageSourcesNode.SetAttribute("default", "")
				#Hack to get around -WarningAction bug
				$PsCmdlet.WriteWarning("There are no more sources configured")
			}
			if($PackageSourcesNode.GetAttribute("default") -eq $Name) {
				$FirstChild = $PackageSourcesNode.FirstChild
				$PackageSourcesNode.SetAttribute("default", $FirstChild.Name)
				#Hack to get around -WarningAction bug
				$PsCmdlet.WriteWarning("The default source ($Name) was removed, the default source is now $($FirstChild.Name)")
			}
		}
	}
	end {
		SaveNuGetConfigXML $NuGetConfigXML
	}
}

<#
.SYNOPSIS

	Changes the properties of a configured source for PS-NuGet.

.DESCRIPTION

.PARAMETER Name

	The name of the source to modify. This parameter is mandatory and cannot currently be edited on a source.  If you need to rename a source you will need to remove it and add it using the new name.
	
.PARAMETER Uri

	The new Uri to use for this source name. If this parameter is omitted, the current Uri will remain configured.
	
.PARAMETER Default

	Switch parameter indicating to mark this source as the default source.  If another source is already marked as default, the current source will replace it as default.

.LINK
	Get-NuGetSource
	Add-NuGetSource
	Remove-NuGetSource
	Set-NuGetSource

.EXAMPLE

	Set-NuGetSource -Name OurLocalPackages -Uri http://psnugetbeta.example.com/nuget
	
	Modifies the Uri of "OurLocalPackages" to http://psnugetbeta.example.com/nuget.  The source that is selected as default is not changed in this operation.

.NOTE

	If you specify -Default:$False on the current default package, a randomly selected source will be marked as default.
	
#>
function Set-NuGetSource {
	param(
		[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
		[ValidateNotNullOrEmpty()]
		[string]$Name,
		
		[Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
		[ValidateNotNullOrEmpty()]
		[Uri]$Uri,
		
		[Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]
		[switch]$Default
	)
	begin {
		$NuGetConfigXML = GetNuGetConfigXML -Create
		if(!$NuGetConfigXml) {
			#I don't expect this will ever be called as the GetNuGetConfigXML function
			#  throws an exception if it can't load an existing file or it just creates an
			#  object which is unlikely to fail.
			throw "Unable to open or create config file"
		}
	}
	process {
		$PackageSourcesNode = $NuGetConfigXML.configuration.packageSources
		$ExistingNode = $PackageSourcesNode.SelectSingleNode("source[@name='$Name']")
		if(!$ExistingNode) {
			#Write an error for this source
			Write-Error "Unable to change source $Name, name not found in existing sources."
			#return will continue processing the pipeline
			return
		}
		if($Uri -and $ExistingNode.GetAttribute("URI") -ne $Uri.ToString()) {
			$ExistingNode.SetAttribute("URI", $Uri.ToString())
		}
		if($PSBoundParameters.ContainsKey("Default")) {
			if($Default) {
				#Default was specified as true
				if($PackageSourcesNode.GetAttribute("default") -ne $Name) {
					#Set the default to this source
					$PackageSourcesNode.SetAttribute("default", $Name)
				}
			} else {
				#Default was specified as false
				$FirstChild = $PackageSourcesNode.FirstChild
				if($FirstChild.Name -eq $Name) {
					if($PackageSourcesNode.ChildNodes.Count -gt 1) {
						$FirstChild = $PackageSourcesNode.ChildNodes.Item(1)
					} else {
						$FirstChild = $null
						Write-Warning "Unable to change default source, there are no other sources to set as default"
					}
				}
				if($FirstChild) {
					Write-Warning "Unsetting default on a source will cause the first listed source to be configured as default."
					$PackageSourcesNode.SetAttribute("default", $FirstChild.Name)
				}
			}
		}
	}
	end {
		SaveNuGetConfigXML $NuGetConfigXML
	}
}

<#
.SYNOPSIS

	Adds a new package subscription to PS-NuGet.

.DESCRIPTION

.PARAMETER Id

	The Id of the package to subscribe to.

.LINK
	Get-NuGetSubscription
	Add-NuGetSubscription
	Remove-NuGetSubscription

.EXAMPLE

	Add-NuGetSubscription -Id PS-NuGet
	
	Adds the PS-NuGet package Id to the list of subscriptions.
	
#>
function Add-NuGetSubscription {
	param(
		[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
		[ValidateNotNullOrEmpty()]
		[string]$Id
	)
	begin {
		$NuGetConfigXML = GetNuGetConfigXML -Create
		if(!$NuGetConfigXml) {
			#I don't expect this will ever be called as the GetNuGetConfigXML function
			#  throws an exception if it can't load an existing file or it just creates an
			#  object which is unlikely to fail.
			throw "Unable to open or create config file"
		}
	}
	process {
		#We can't just use Get-NuGetSource because it loads stale data from the XML file,
		#  which we aren't saving till the end if this is pipelined.
		$ExistingNode = $NuGetConfigXML.configuration.subscriptions.subscription | ?{$_ -eq $Id}
		if(!$ExistingNode) {
			$SubscriptionNode = $NuGetConfigXML.CreateElement("subscription")
			$SubscriptionNode.InnerText = $Id
			
			$Subscriptions = $NuGetConfigXML.SelectSingleNode("/configuration/subscriptions")
			$Subscriptions.AppendChild($SubscriptionNode) | Out-Null
			Write-Verbose "Added subscription for $id"
			Write-Debug "Added subscription for $id"
		} else {
			Write-Debug "A subscription for $Id already exists"
		}
	}
	end {
		SaveNuGetConfigXML $NuGetConfigXML
	}
}

<#
.SYNOPSIS

	Get a PS-NuGet package subscription.

.DESCRIPTION

.PARAMETER Id

	The Id of the package subscribe to get.

.LINK
	Get-NuGetSubscription
	Add-NuGetSubscription
	Remove-NuGetSubscription

.EXAMPLE

	Get-NuGetSubscription -Id PS-NuGet
	
	Gets the package subscription for the PS-NuGet package
	
#>
function Get-NuGetSubscription {
	param(
		[Parameter(Mandatory=$false)]
		[ValidateNotNullOrEmpty()]
		[string]$Id
	)
	$NuGetConfigXML = GetNuGetConfigXML
	if($NuGetConfigXML) {
		$Subscriptions = $NuGetConfigXML.SelectSingleNode("/configuration/subscriptions")
		$Subscriptions.ChildNodes | %{
			#Stuff this in an object so it can be pipelined to other cmdlets.
			$Out = "" | Select Id
			$Out.Id = $_.InnerText
			$Out
		} | ?{
			if($Id) {
				#If -Id was specified, filter on it
				$_.Id -like $Id
			} else {
				#otherwise return all subscriptions
				$true
			}
		}
	}
}

<#
.SYNOPSIS

	Removes a package subscription from PS-NuGet.

.DESCRIPTION

.PARAMETER Id

	The Id of the package to un-subscribe from.

.LINK
	Get-NuGetSubscription
	Add-NuGetSubscription
	Remove-NuGetSubscription

.EXAMPLE

	Remove-NuGetSubscription -Id PS-NuGet
	
	Removes the subscription for the PS-NuGet package.
	
#>
function Remove-NuGetSubscription {
	param(
		[Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
		[ValidateNotNullOrEmpty()]
		[string]$Id
	)
	begin {
		$NuGetConfigXML = GetNuGetConfigXML
	}
	process {
		if($NuGetConfigXML) {
			#We can't just use Get-NuGetSubscription because it loads stale data from the XML file,
			#  which we aren't saving till the end if this is pipelined.
			$ExistingNode = $NuGetConfigXML.SelectNodes("/configuration/subscriptions/subscription") | ?{ $_.InnerText -eq $Id }
			if($ExistingNode) {
				$Subscriptions = $NuGetConfigXML.SelectSingleNode("/configuration/subscriptions")
				$Subscriptions.RemoveChild($ExistingNode) | Out-Null
				Write-Verbose "Removed subscription for $id"
				Write-Debug "Removed subscription for $id"
			} else {
				Write-Warning "Unable to locate a subscription for $Id"
			}
		} else {
			Write-Warning "Unable to locate a subscription for $Id"
		}
	}
	end {
		SaveNuGetConfigXML $NuGetConfigXML
	}
}